/*
Copyright © 2023 LiuBo <liub233@qq.com>
*/
package main

import (
	"fmt"
	tag "gitee.com/liubocode/tag-for-cobra"
	"github.com/spf13/cobra"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

type ArgObj struct {
	OutputPath string `name:"output" shorthand:"o" desc:"输出路径文件的完整目录"`
	GzipEnable bool   `name:"gzip" shorthand:"z" desc:"是否压缩"`
	Export     bool   `name:"export" shorthand:"e" desc:"是否是导出模式。导出模式用于导出pkg.tar(.gz)到当前环境的GOPATH中。"`
}

const outputFilePref = "pkg"

var obj = &ArgObj{}

var rootCmd = &cobra.Command{
	Use:   "go-freeze [OPTIONS] go.mod[或打包的依赖文件]",
	Short: "用于打包项目依赖",
	Long: `与python的freeze库类似的功能，将go.mod文件的依赖打包，供移动到无外网环境使用。
输出文件名称为pkg.tar，若带压缩参数-z(--gzip)则为pkg.tar.gz。`,
	Run: rootRun,
}

const tempPath string = "mod-download-cache"

func doFreeze(gomodFile string) error {
	if !strings.HasSuffix(gomodFile, "go.mod") {
		return fmt.Errorf("go.mod文件错误")
	}

	tpath, err := os.MkdirTemp("/tmp", tempPath)
	if err != nil {
		return err
	}
	defer os.Remove(tpath)

	log.Println("GOPATH=", tpath)
	workDir := filepath.Join(tpath, "src", "tempProject")
	err = os.MkdirAll(workDir, os.ModePerm)
	if err != nil {
		return err
	}

	oldPath := os.Getenv("GOPATH")
	os.Setenv("GOPATH", tpath)
	defer os.Setenv("GOPATH", oldPath)

	fsrc, err := os.Open(gomodFile)
	if err != nil {
		return err
	}

	defer fsrc.Close()
	fdst, err := os.OpenFile(filepath.Join(workDir, "go.mod"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777)
	if err != nil {
		return err
	}
	defer fdst.Close()

	_, err = io.Copy(fdst, fsrc)
	if err != nil {
		return err
	}
	log.Println("正在下载...")
	execCmdAndPrint("go", "mod", "download")
	log.Println("下载完成")
	log.Println("打包...")

	flags := "-cf"
	outFileName := outputFilePref + ".tar"
	if obj.GzipEnable {
		flags = "-czf"
		outFileName = outputFilePref + ".tar.gz"
	}

	if obj.OutputPath != "" {
		outFileName = filepath.Join(obj.OutputPath, outFileName)
	}
	os.Remove(outFileName)

	execCmdAndPrint("tar", flags, outFileName, "-C", tpath, "pkg")
	log.Println("完成...")
	return nil
}

func doExport(goDepFile string) error {
	tpath, err := os.MkdirTemp("/tmp", tempPath)
	if err != nil {
		return err
	}
	defer os.Remove(tpath)

	tempPkt := filepath.Join(tpath, "pkg", "mod", "*")
	envPkg := filepath.Join(os.Getenv("GOPATH"), "pkg", "mod")
	os.MkdirAll(envPkg, os.ModePerm)

	log.Println("tempDirPkg=", tempPkt)
	log.Println("GOPATH_PKG=", envPkg)

	flags := "-xf"
	if strings.Contains(goDepFile, "gz") {
		flags = "-zxf"
	}

	execCmdAndPrint("tar", flags, goDepFile, "-C", tpath)
	log.Println(tempPkt, "-->", envPkg)
	cpCmd := fmt.Sprintf("cp -rf %s %s", tempPkt, envPkg)
	execCmdAndPrint("sh", "-c", cpCmd)
	log.Println("处理完成")
	return err
}

func rootRun(cmd *cobra.Command, args []string) {
	if len(args) != 1 {
		log.Fatalln("参数错误，需要go.mod文件（或输出文件）的路径作为参数")
		os.Exit(1)
		return
	}
	var err error
	if obj.Export {
		err = doExport(args[0])
	} else {
		err = doFreeze(args[0])
	}

	if err != nil {
		log.Fatalln(err)
		os.Exit(1)
	}
}

func execCmdAndPrint(name string, arg ...string) {
	osc := exec.Command(name, arg...)
	out, err := osc.CombinedOutput()
	if err != nil {
		log.Println(string(out))
		log.Fatal("执行命令失败，指令：", name, ", 参数：", arg, ", err：", err)
		os.Exit(1)
		return
	}
	log.Println(string(out))
}

func main() {
	tag.ParseExecute(obj, rootCmd)
}
